/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <csignal>
#include <pthread.h>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
#ifdef QtWorkaround
#include <QCoreApplication>
#endif
#include "PluginManager.h"

using namespace std;

namespace {
	void waitForQuit(sigset_t oldSigs, bool debug)
	{
		// Restore signals
		pthread_sigmask(SIG_SETMASK, &oldSigs, 0);

		// Wait for signals
		LOG4CXX_INFO(log4cxx::Logger::getRootLogger(), "Waiting for signals");
		sigset_t waitSigs;
		sigemptyset(&waitSigs);
		if(!debug) {
			sigaddset(&waitSigs, SIGINT);
			sigaddset(&waitSigs, SIGQUIT);
		}
		sigaddset(&waitSigs, SIGTERM);
		pthread_sigmask(SIG_BLOCK, &waitSigs, 0);
		int sig;
		sigwait(&waitSigs, &sig);
		assert(sig == SIGINT || (!debug && (sig == SIGQUIT || sig == SIGTERM)));
		LOG4CXX_INFO(log4cxx::Logger::getRootLogger(), "Got signal " << sig << ". Cancelling operations and waiting for threads.");
#ifdef QtWorkaround
		// Need to make QCoreApplication::exec() return
		QCoreApplication::quit();
#endif
	}
}

int main(int argc, char **argv)
{
	try
	{
		// Set up logging
		// TODO: Log to file. See http://wiki.apache.org/logging-log4cxx/FrequentlyAskedQuestions on how to do this programmatically
		log4cxx::BasicConfigurator::configure();
		log4cxx::Logger::getRootLogger()->setLevel(log4cxx::Level::getInfo());

		bool debug = false;
		string configFileName = "jamus.conf";

		ostringstream usage;
		usage << "Usage: " << argv[0] << " [config file] [-d]\n"
			" If [config file] is not specified, " << configFileName << " is used.\n"
			" If -d is specified, SIGINT and SIGQUIT will not be caught.\n";

		if(argc == 2) {
			if(string(argv[1]) == "-d")
				debug = true;
			else
				configFileName = argv[1];
		} else if(argc == 3) {
			if(string(argv[1]) == "-d") {
				debug = true;
				configFileName = argv[2];
			} else if(string(argv[2]) == "-d") {
				debug = true;
				configFileName = argv[1];
			} else
				throw runtime_error(usage.str());
		} else if(argc > 3)
			throw runtime_error(usage.str());

#ifdef QtWorkaround
		int argc2 = 1;
		QCoreApplication qtKernel(argc2, argv);
		LOG4CXX_TRACE(log4cxx::Logger::getRootLogger(), "qt core app runs");
#endif

		// Read configuration
		boost::property_tree::ptree config;
		boost::property_tree::read_xml(configFileName, config);

		const string backend = config.get<string>("config.core.backend");
		const string backend_path = backend + "_backend.so";
		LOG4CXX_INFO(log4cxx::Logger::getRootLogger(), "using \"" + backend_path + "\" as backend");

		vector<string> frontend_paths;
		BOOST_FOREACH(const boost::property_tree::ptree::value_type& element, config.get_child("config.core")) {
			if(element.first == "frontend") {
				const string& f = element.second.data();
				frontend_paths.push_back(f + "_frontend.so");
				LOG4CXX_INFO(log4cxx::Logger::getRootLogger(), "using \"" + frontend_paths.back() + "\" as frontend");
			}
		}

		// Load plugins
		PluginManager plugins(backend_path, frontend_paths, config);

		// Block signals for this thread (and subsequently created threads)
		sigset_t allSigs, oldSigs;
		sigfillset(&allSigs);
		pthread_sigmask(SIG_BLOCK, &allSigs, &oldSigs);

		// Run back-end and front-ends in their own thread
		boost::thread backendThread(boost::bind(&backend::Plugin::run, &plugins.getBackend()));
		vector< boost::shared_ptr<boost::thread> > frontendThreads;
		BOOST_FOREACH(frontend::Plugin* f, plugins.getFrontends())
			frontendThreads.push_back(boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&frontend::Plugin::run, f))));

#ifdef QtWorkaround
		boost::thread fakedMainThread(boost::bind(waitForQuit, oldSigs, debug));
		LOG4CXX_TRACE(log4cxx::Logger::getRootLogger(), "QCoreApplication::exec() started");
		QCoreApplication::exec();
		LOG4CXX_TRACE(log4cxx::Logger::getRootLogger(), "QCoreApplication::exec() finished");
		fakedMainThread.join();
#else
		waitForQuit(oldSigs, debug);
#endif

		BOOST_FOREACH(frontend::Plugin* f, plugins.getFrontends())
			f->cancel();
		BOOST_FOREACH(boost::shared_ptr<boost::thread> t, frontendThreads)
			t->join();

		plugins.getBackend().cancel();
		backendThread.join();

		LOG4CXX_INFO(log4cxx::Logger::getRootLogger(), "Terminating");
	}

	catch(exception& e)
	{
		LOG4CXX_ERROR(log4cxx::Logger::getRootLogger(), e.what());
		return 1;
	}

	return 0;
}
